/**
 * Copyright (C) 2009 Noel Ang
 *
 * This file is part of Samsara.
 *
 * Samsara is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Samsara is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Samsara.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package com.lunarshades.samsara.ui;

import java.awt.Component;
import java.awt.Container;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

/**
 * Dialog for displaying some arbitrary, user-defined visual component as its
 * primary interaction interface, followed by a read-only listing of
 * user-defined and -supplied elements for previewing purposes. Implemented for
 * displaying various karma-related operations, with a preview of affected
 * people.
 *
 * @author Noel Ang, sparta.variable@gmail.com
 */
public class ListingDialog<B> {

public ListingDialog(String title,
                     String batchCaption,
                     Container host,
                     JPanel... components
) {

    // Panel that will contain the read-only list
    mBatchDisplayPanel = new JPanel();

    // Optional callback for user components to invoke to inform the
    // dialog that the interaction is completed with a commit
    // (versus cancel) semantic
    CommitAction = new CommitAction();

    // The actual Swing dialog for this controller
    mDelegate = initComponents(title, batchCaption, host, components);
}

private JDialog initComponents(String title,
                               String caption,
                               Container host,
                               JPanel[] components
) {

    // This panel will contain all of the user's components AND our list
    JPanel aDialogPanel = new JPanel();
    GridBagLayout layout = new GridBagLayout();
    GridBagConstraints cons = new GridBagConstraints();
    aDialogPanel.setLayout(layout);

    // Each user component is laid out vertically into the panel
    cons.anchor = GridBagConstraints.CENTER;
    cons.fill = GridBagConstraints.HORIZONTAL;
    cons.gridx = 0;
    cons.weightx = 1.0;
    cons.insets = new Insets(3, 5, 3, 5);
    for (JPanel panel : components) {
        aDialogPanel.add(panel, cons);
    }

    // Add our labeled list next
    mBatchDisplayPanel.setLayout(layout);
    mListArea = new JTextArea();
    mListArea.setLineWrap(true);
    mListArea.setWrapStyleWord(true);
    mListArea.setEditable(false);
    mListArea.setFocusable(false);
    mListArea.setBackground(aDialogPanel.getBackground());
    mListArea.setMargin(new Insets(2, 4, 2, 4));
    mListArea.setRows(4);
    JScrollPane aScrollPane = new JScrollPane(mListArea,
            JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
            JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
    );
    updateList();

    JLabel captionLabel = new JLabel(caption);
    captionLabel.setFont(captionLabel.getFont().deriveFont(Font.BOLD));
    mBatchDisplayPanel.add(captionLabel, cons);
    cons.fill = GridBagConstraints.BOTH;
    cons.weighty = 1.0;
    mBatchDisplayPanel.add(aScrollPane, cons);
    aDialogPanel.add(mBatchDisplayPanel, cons);

    // Execute and Cancel buttons
    JButton button;
    List<Component> buttonList = new LinkedList<Component>();
    buttonList.add((button = new JButton(CommitAction)));
    button.setDefaultCapable(true);
    buttonList.add((button = new JButton("Cancel")));
    button.setMnemonic('c');
    button.setDefaultCapable(false);
    button.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            mDelegate.setVisible(false);
            mIsCanceled = true;
        }
    }
    );
    Utils.equalizePreferredSizes(buttonList);

    // Create our delegate dialog using JOptionPane.
    // Our self-defined "message" for the dialog is our entire panel.
    // We use our own buttons too instead of the defaults available from JOptionPane
    JOptionPane pane = new JOptionPane();
    pane.setMessage(aDialogPanel);
    pane.setOptions(buttonList.toArray());
    return pane.createDialog(findFrame(host), title);
}

private void updateList() {
    synchronized (mBatch) {
        StringBuffer buffer = new StringBuffer();
        for (B element : mBatch) {
            buffer.append(element.toString()).append(LISTING_DELIM);
        }
        if (!mBatch.isEmpty()) {
            int bufferLength = buffer.length();
            buffer.delete(bufferLength - LISTING_DELIM.length(), bufferLength);
        }
        mListArea.setText(buffer.toString());
    }
}

private Frame findFrame(Container container) {
    // Keep walking up the container's ancestor hierarchy until we find a Frame
    // or we reach the top.
    if (container != null) {
        Container parent;
        if (container instanceof Frame) {
            return (Frame) container;
        }
        while ((parent = container.getParent()) != null) {
            if (parent instanceof Frame) {
                return (Frame) parent;
            }
            container = parent;
        }
    }
    return null;
}

public void clearBatch() {
    synchronized (mBatch) {
        mBatch.clear();
    }
}

public boolean addToBatch(B element) {
    synchronized (mBatch) {
        return element != null && mBatch.add(element);
    }
}

public Set<B> getBatch() {
    synchronized (mBatch) {
        return Collections.unmodifiableSortedSet(mBatch);
    }
}

public synchronized void show() {
    // Update list model with latest batch data
    updateList();

    // Display dialog
    mIsCanceled = true;
    mDelegate.pack();
    mDelegate.setVisible(true);
}

public boolean isCanceled() {
    return mIsCanceled;
}

private class CommitAction extends AbstractAction {
    private CommitAction() {
        super(COMMIT_ACTION_NAME);
        putValue(Action.MNEMONIC_KEY, KeyEvent.VK_E);
    }

    public void actionPerformed(ActionEvent e) {
        mIsCanceled = false;
        mDelegate.setVisible(false);
    }

    private static final String COMMIT_ACTION_NAME = "Execute";
}

private static final String LISTING_DELIM = ", ";

private final JDialog mDelegate;
private final JPanel mBatchDisplayPanel;
private boolean mIsCanceled = true;
private final SortedSet<B> mBatch = new TreeSet<B>();

/**
 * Callback object for this Dialog that users invoke to signal that the
 * interaction is completed with a "commit" semantic.
 */
public final Action CommitAction;
private JTextArea mListArea;
}
